<!--
Copyright 2013 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<link rel="import" href="../polymer-key-helper/polymer-key-helper.html">

<!--
/**
 * @module Polymer Elements
 */
/**
 * polymer-overlay displays overlayed on top of other content. It starts
 * out hidden and is displayed by setting it's opened property to true.
 * A polymer-overlay's opened state can be toggled by calling the toggle
 * method.
 * 
 * It's common to want a polymer-overlay to animate to its opened
 * position. A number of helper css classes provide some basic open/close
 * animations. For example, assigning the class polymer-overlay-fade to a
 * polymer-overlay will make it fade into and out of view as it opens and
 * closes. Note, if multiple polymer-overlay's are opened, they should
 * stack on top of each other.
 * 
 * Styling: The size and position of a polymer-overlay should be setup
 * via css.
 * polymer-overlay is natually sized around its content. When a
 * polymer-overlay is opened it is shown and the 'opened' class is added
 * to it. This is typically where css transitions and animations are
 * applied. When the polymer-overlay is closed, the 'opened' class is
 * removed and a 'closing' class is added. Use 'closing' to customize
 * the closing animation. 
 * 
 * Classes for animating polymer-overlay:
 * 
 * * polymer-overlay-fade: fade in/out when opened/closed
 * * polymer-overlay-scale-slideup: open: fade in and shrink;
 * close: slide up
 * * polymer-overlay-shake: open: fly in and shake; close: shake and
 * fly out.
 *
 * It's common to use polymer-overlay to gather user input, for example
 * a login dialog. To facilitate this, polymer-overlay supports automatic
 * focusing of a specific element when it's opened. The element to be
 * focused should be given an autofocus attribute.
 * 
 * An element that should close the polymer-overlay will automatically
 * do so if it is given the overlay-toggle attribute. Please note that
 * polymer-overlay will close whenever the user taps outside it or
 * presses the escape key. The behavior can be turned off via the
 * autoCloseDisabled property.
 * 
 *     <div>
 *       <polymer-overlay></polymer-overlay>
 *	     <h2>Dialog</h2>
 *       <input placeholder="say something..." autofocus"></input>
 *       <div>I agree with this wholeheartedly.</div>
 *       <button overlay-toggle>OK</button>
 *     </div>
 * 
 * 
 * @class polymer-overlay
 */
/**
 * Fired when the polymer-overlay opened property is set.
 * 
 * @event open
 * @param {Object} inDetail
 * @param {Object} inDetail.opened the opened state
 */
-->
<polymer-element name="polymer-overlay" attributes="target opened autoCloseDisabled">
	<template>
		<link rel="stylesheet" href="polymer-overlay.css">
		<link rel="stylesheet" polymer-scope="global" href="polymer-overlay-global.css">
		<style>
			@host {
				* {
					display: none;
				}
			}
		</style>
		<polymer-key-helper id="keyHelper"></polymer-key-helper>
	</template>
	<script>
		(function() {
			// track overlays for z-index and focus managemant
			var overlays = [];
			var trackOverlays = function(inOverlay) {
				if (inOverlay.opened) {
					var z0 = currentOverlayZ();
					overlays.push(inOverlay);
					var z1 = currentOverlayZ();
					if (z1 <= z0) {
						applyOverlayZ(inOverlay, z0);
					} 
				} else {
					var i = overlays.indexOf(inOverlay);
					if (i >= 0) {
						overlays.splice(i, 1);
						setZ(inOverlay, null);
					}
				}
			}
			
			var applyOverlayZ = function(inOverlay, inAboveZ) {
				setZ(inOverlay.target, inAboveZ + 2);
			}
			
			var setZ = function(inNode, inZ) {
				inNode.style.zIndex = inZ;
			}
		
			var currentOverlay = function() {
				return overlays[overlays.length-1];
			}
			
			var DEFAULT_Z = 10;
			
			var currentOverlayZ = function() {
				var z;
				var current = currentOverlay();
				if (current) {
					var z1 = window.getComputedStyle(current.target).zIndex;
					if (!isNaN(z1)) {
						z = Number(z1);
					}
				}
				return z || DEFAULT_Z;
			}
			
			var focusOverlay = function() {
				var current = currentOverlay();
				if (current) {
					current.applyFocus();
				}
			}
		
			Polymer('polymer-overlay', {
				/**
				 * The target element.
				 *
				 * @attribute target
				 * @type Object
				 */
				target: null,
				/**
				 * Set opened to true to show an overlay and to false to hide it.
				 * A polymer-overlay may be made intially opened by setting its
				 * opened attribute.
				 * @attribute opened
				 * @type boolean
				 * @default false
				 */
				opened: false,
				/**
				 * By default an overlay will close automatically if the user
				 * taps outside it or presses the escape key. Disable this
				 * behavior by setting the autoCloseDisabled property to true.
				 * @attribute autoCloseDisabled
				 * @type boolean
				 * @default false
				 */
				autoCloseDisabled: false,
				timeout: 1000,
				captureEventType: 'tap',
				ready: function() {
					if (this.tabIndex === undefined) {
						this.tabIndex = -1;
					}
					this.setAttribute('touch-action', 'none');
				},
				/** 
				 * Toggle the opened state of the overlay.
				 * @method toggle
				 */
				toggle: function() {
					this.opened = !this.opened;
				},
				targetChanged: function(old) {
					if (this.target) {
						if (this.target.tabIndex === undefined) {
							this.target.tabIndex = -1;
						}
						this.target.classList.add('polymer-overlay');
						this.addListeners(this.target);
					}
					if (old) {
						old.classList.remove('polymer-overlay');
						this.removeListeners(this.target);
					}
				},
				listeners: {
					'webkitAnimationStart': 'openedAnimationStart',
					'animationStart': 'openedAnimationStart',
					'webkitAnimationEnd': 'openedAnimationEnd',
					'animationEnd': 'openedAnimationEnd',
					'webkitTransitionEnd': 'openedTransitionEnd',
					'transitionEnd': 'openedTransitionEnd',
					'tap': 'tapHandler',
					'keydown': 'keydownHandler'
				},
				addListeners: function(node) {
					for (e in this.listeners) {
						node.addEventListener(e, this[this.listeners[e]].bind(this));
					}
				},
				removeListeners: function(node) {
					for (e in this.listeners) {
						node.removeEventListener(e, this[this.listeners[e]].bind(this));
					}
				},
				openedChanged: function() {
					this.renderOpened();
					trackOverlays(this);
					if (!this.autoCloseDisabled) {
						this.enableCaptureHandler(this.opened);
					}
					this.enableResizeHandler(this.opened);
					this.fire('opened', this.opened);
				},
				enableHandler: function(inEnable, inMethodName, inNode, inEventName, inCapture) {
					var m = 'bound' + inMethodName;
					this[m] = this[m] || this[inMethodName].bind(this);
					
					inNode[inEnable ? 'addEventListener' : 'removeEventListener'](
						inEventName, this[m], inCapture);
				},
				enableResizeHandler: function(inEnable) {
					this.enableHandler(inEnable, 'resizeHandler', window, 
						'resize');
				},
				enableCaptureHandler: function(inEnable) {
					this.enableHandler(inEnable, 'captureHandler', document, 
						this.captureEventType, true);
				},
				getFocusNode: function() {
					return this.target.querySelector('[autofocus]') || this.target;
				},
				// TODO(sorvell): nodes stay focused when they become un-focusable
				// due to an ancestory becoming display: none; file bug.
				applyFocus: function() {
					var focusNode = this.getFocusNode();
					if (this.opened) {
						focusNode.focus();
					} else {
						focusNode.blur();
						focusOverlay();
					}
				},
				renderOpened: function() {
					this.target.classList.remove('closing');
					this.target.classList.add('revealed');
					// continue styling after delay so display state can change
					// without aborting transitions
					this.asyncMethod('continueRenderOpened');
				},
				continueRenderOpened: function() {
					this.target.classList.toggle('opened', this.opened);
					this.target.classList.toggle('closing', !this.opened);
					//this.animating = this.asyncMethod('completeOpening', null, this.timeout);
				},
				completeOpening: function() {
					//clearTimeout(this.animating);
					this.animating = null;
					this.target.classList.remove('closing');
					this.target.classList.toggle('revealed', this.opened);
					this.applyFocus();
				},
				openedAnimationEnd: function(e) {
					if (!this.opened) {
						this.target.classList.remove('animation');
					}
					// same steps as when a transition ends
					this.openedTransitionEnd(e);
				},
				openedTransitionEnd: function(e) {
					// TODO(sorvell): Necessary due to 
					// https://bugs.webkit.org/show_bug.cgi?id=107892
					// Remove when that bug is addressed.
					if (e.target == this.target) {
						this.completeOpening();
						e.stopPropagation();
						e.cancelBubble = true;
					}
				},
				openedAnimationStart: function(e) {
					this.target.classList.add('animation');
					e.stopPropagation();
					e.cancelBubble = true;
				},
				tapHandler: function(e) {
					if (e.target && e.target.hasAttribute('overlay-toggle')) {
						this.toggle();
					} else {
						if (this.autoCloseJob) {
							this.autoCloseJob.stop();
							this.autoCloseJob = null;
						}
					}
				},
				// TODO(sorvell): This approach will not work with modal. For
				// this we need a scrim.
				captureHandler: function(e) {
					if (!this.autoCloseDisabled && (currentOverlay() == this) && (this 
							!= e.target) && !(this.contains(e.target))) {
						this.autoCloseJob = this.job(this.autoCloseJob, function() {
							this.opened = false;
						});
					}
				},
				keydownHandler: function(e) {
					if (!this.autoCloseDisabled && (e.keyCode == this.$.keyHelper.ESCAPE_KEY)) {
						this.opened = false;
						e.stopPropagation();
						e.cancelBubble = true;
					}
				},
				/**
				 * Extensions of polymer-overlay should implement the resizeHandler
				 * method to adjust the size and position of the overlay when the 
				 * browser window resizes.
				 * @method resizeHandler
				 */
				resizeHandler: function() {
				}
			});
		})();
	</script>
</polymer-element>
